package com.zlcx.tz.live.service.impl;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.zlcx.tz.live.client.UserClient;
import com.zlcx.tz.live.client.UserFriendClient;
import com.zlcx.tz.live.client.vo.UserRelationVO;
import com.zlcx.tz.live.constant.YN;
import com.zlcx.tz.live.domain.TimeLearnRoomDO;
import com.zlcx.tz.live.domain.TimeLearnRoomSeatsDO;
import com.zlcx.tz.live.domain.TimeLearnRoomUsersDO;
import com.zlcx.tz.live.domain.backup.TimeLearnRoomSeatsBackUpDO;
import com.zlcx.tz.live.domain.backup.TimeLearnRoomUsersBackUpDO;
import com.zlcx.tz.live.exception.RoomSeatException;
import com.zlcx.tz.live.exception.RoomSeatUsedException;
import com.zlcx.tz.live.exception.UnRoomUserException;
import com.zlcx.tz.live.exception.UserTimeLearningException;
import com.zlcx.tz.live.processor.chain.JoinRoomPostProcessorChain;
import com.zlcx.tz.live.processor.chain.QuitRoomPostProcessorChain;
import com.zlcx.tz.live.processor.chain.SitUpPostProcessorChain;
import com.zlcx.tz.live.processor.chain.TakeSeatPostProcessorChain;
import com.zlcx.tz.live.repository.TimeLearnRoomRepository;
import com.zlcx.tz.live.repository.TimeLearnRoomSeatsRepository;
import com.zlcx.tz.live.repository.TimeLearnRoomUsersRepository;
import com.zlcx.tz.live.repository.backup.TimeLearnRoomSeatsBackUpRepository;
import com.zlcx.tz.live.repository.backup.TimeLearnRoomUsersBackUpRepository;
import com.zlcx.tz.live.service.TimeLearnRoomDialogStore;
import com.zlcx.tz.live.service.TimeLearnRoomService;
import com.zlcx.tz.live.vo.TimeLearnRoomSeatsDTO;
import com.zlcx.tz.live.vo.TimeLearnRoomSeatsVO;
import com.zlcx.tz.live.vo.TimeLearnRoomVO;
import com.zlcx.tz.live.vo.UserVO;
import com.zlcx.tz.live.web.BaseController;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.http.util.Asserts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.zlcx.tz.live.domain.TimeLearnRoomUsersDO.TimeLearnRoomUserState.FREE_TIME;
import static com.zlcx.tz.live.domain.TimeLearnRoomUsersDO.TimeLearnRoomUserState.LEARNING_TIME;

/**
 * @author anjl
 * @date 6:23 PM 2019/7/11
 * description:
 */
@Slf4j
@Service
public class TimeLearnRoomServiceImpl implements TimeLearnRoomService {

    @Transactional(rollbackFor = Exception.class)
    @Override
    public TimeLearnRoomDO save(TimeLearnRoomDO room) {
        return timeLearnRoomRepository.save(room);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void joinRoom(Long roomId, Long userId) throws UserTimeLearningException, UnRoomUserException {

        if (Objects.isNull(roomId) || Objects.isNull(userId)) {
            throw new IllegalArgumentException("参数不能为 null ! roomId = " + roomId + " userId = " + userId);
        }
        TimeLearnRoomUsersDO roomUsersDO;
        TimeLearnRoomUsersDO roomUser = timeLearnRoomUsersRepository.findByUserId(userId);
        //用户在此房间中，直接返回
        if (Objects.nonNull(roomUser) && roomId.equals(roomUser.getRoomId())) {
            return;
        }

        //用户如果在其他房间中，先退出
        if (Objects.nonNull(roomUser) && !roomId.equals(roomUser.getRoomId())) {
            quitRoom(roomUser.getRoomId(), userId);
        }

        //加入房间
        roomUsersDO = TimeLearnRoomUsersDO
                .builder()
                .joinTime(new Date())
                .roomId(roomId)
                .state(FREE_TIME.name())
                .userId(userId)
                .build();
        timeLearnRoomUsersRepository.save(roomUsersDO);
        //加入房间的后置处理、消息
        joinRoomPostProcessorChain.processor(roomUsersDO);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void quitRoom(Long roomId, Long userId) throws UnRoomUserException, UserTimeLearningException {

        if (Objects.isNull(roomId) || Objects.isNull(userId)) {
            throw new IllegalArgumentException("参数不能为 null ! roomId = " + roomId + " userId = " + userId);
        }

        TimeLearnRoomUsersDO roomUser = timeLearnRoomUsersRepository.findByUserId(userId);
        if (Objects.isNull(roomUser) || !roomId.equals(roomUser.getRoomId())) {
            throw new UnRoomUserException("在该房间内没有查询到此用户 roomId = " + roomId + " userId = " + userId);
        }

        if (LEARNING_TIME.name().equals(roomUser.getState())) {
            throw new UserTimeLearningException("用户正在计时自习室学习中，请先结束学习再退出房间 roomId = " + roomId + " userId = " + userId);
        }

        //备份之前的加入房间信息, 删除当前用户在房间内的数据
        recordRoomUserInfo(roomUser);
        timeLearnRoomUsersRepository.deleteById(roomUser.getId());
        log.info("退出房间，删除数据成功！roomUser = {}", roomUser);
        quitRoomPostProcessorChain.processor(roomUser);

    }

    private void recordRoomUserInfo(TimeLearnRoomUsersDO roomUser) {
        TimeLearnRoomUsersBackUpDO backup = TimeLearnRoomUsersBackUpDO.from(roomUser);
        timeLearnRoomUsersBackUpRepository.save(backup);
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public TimeLearnRoomSeatsVO takeSeat(Long seatId, Long userId, Long contentId, String content, Integer minute, Integer useOtherApps)
            throws RoomSeatException, UnRoomUserException, RoomSeatUsedException, UserTimeLearningException {

        if (Objects.isNull(seatId)
                || Objects.isNull(userId)
                || Objects.isNull(contentId)
                || StringUtils.isBlank(content)
                || Objects.isNull(useOtherApps)
                || Objects.isNull(minute)
        ) {
            throw new IllegalArgumentException("参数不能为 null ! seatId = " + seatId + " userId = " + userId + " contentId = " + contentId + " content = " + content + " useOtherApps = " + useOtherApps + " minute = " + minute);
        }

        Asserts.check(minute > 0 && minute < 10000, "请输入合法分钟数 minute = " + minute);
        TimeLearnRoomSeatsDO existSeat = timeLearnRoomSeatsRepository.findByUserId(userId);
        if (Objects.nonNull(existSeat)) {
            throw new UserTimeLearningException("用户正在计时自习室学习中，不能重复占用座位 existSeatId = " + existSeat.getId() + "userId = " + userId);
        }

        TimeLearnRoomSeatsDO seat = lockSeat(seatId);
        if (Objects.isNull(seat)) {
            throw new RoomSeatException("座位不存在或者不合法 userId = " + userId);
        }
        TimeLearnRoomUsersDO roomUser = timeLearnRoomUsersRepository.findByUserId(userId);

        //校验
        if (Objects.isNull(roomUser) || !roomUser.getRoomId().equals(seat.getRoomId())) {
            throw new UnRoomUserException("用户座位信息异常-找不到用户在房间中的信息！seatId = " + seatId + " userId = " + userId);
        }
        if (Objects.nonNull(seat.getUserId())) {
            throw new RoomSeatUsedException("座位已经被占用，请选择其他座位 seatId = " + seatId + " userId = " + userId);
        }

        //入座
        seat.putUser(userId, contentId, content, minute, useOtherApps);
        timeLearnRoomSeatsRepository.save(seat);
        roomUser.setState(LEARNING_TIME.name());
        timeLearnRoomUsersRepository.save(roomUser);

        //入座后记录学习计时信息、更新房间信息、发送消息等
        TimeLearnRoomSeatsDTO seatsDTO = TimeLearnRoomSeatsDTO.from(seat);
        takeSeatPostProcessorChain.processor(seatsDTO);
        return TimeLearnRoomSeatsVO.from(seat);

    }

    private TimeLearnRoomSeatsDO lockSeat(Long seatId) {
        return timeLearnRoomSeatsRepository.findById(seatId);
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void sitUpAndQuit(Long roomId, Long userId) throws RoomSeatException {

        if (Objects.isNull(roomId) || Objects.isNull(userId)) {
            throw new IllegalArgumentException("参数不能为 null ! roomId = " + roomId + " userId = " + userId);
        }
        TimeLearnRoomUsersDO roomUser = timeLearnRoomUsersRepository.findByUserId(userId);
        if (Objects.isNull(roomUser) || !roomId.equals(roomUser.getRoomId())) {
            throw new RoomSeatException("用户座位信息异常-找不到用户在房间中的信息！roomId = " + roomId + "userId = " + userId);
        }

        //如果正在学习，离开座位
        TimeLearnRoomSeatsDO seat = null;
        if (LEARNING_TIME.name().equals(roomUser.getState())) {
            seat = sitUp(roomUser);
        }

        //退出房间
        try {
            quitRoom(roomUser.getRoomId(), userId);
        } catch (UnRoomUserException | UserTimeLearningException e) {
            log.error("", e);
        }
        TimeLearnRoomSeatsVO.from(seat);
    }

    @Transactional(rollbackFor = Exception.class)
    TimeLearnRoomSeatsDO sitUp(TimeLearnRoomUsersDO roomUser) {

        Long roomId = roomUser.getRoomId(), userId = roomUser.getUserId();
        TimeLearnRoomSeatsDO seat;
        seat = timeLearnRoomSeatsRepository.findByUserId(roomUser.getUserId());
        if (Objects.isNull(seat)) {
            throw new IllegalArgumentException("没有查询到对应的房间-用户座位信息！roomId = " + roomId + " userId = " + userId);
        }
        seat = lockSeat(seat.getId());
        Long seatId = seat.getId();
        if (!userId.equals(seat.getUserId())) {
            throw new IllegalArgumentException("查询到对应的房间-座位上用户与登录用户不匹配！seatId = " + seatId + " userId = " + userId + " seatUserId = " + seat.getUserId());
        }
        //将之前的学习信息备份
        recordSeatInfo(roomUser.getId(), seat);
        TimeLearnRoomSeatsDTO seatsDTO = TimeLearnRoomSeatsDTO.from(seat);

        //清空座位
        seat.clear();
        timeLearnRoomSeatsRepository.save(seat);
        roomUser.setState(FREE_TIME.name());
        timeLearnRoomUsersRepository.save(roomUser);

        //结束学习的后置操作
        sitUpPostProcessorChain.processor(seatsDTO);
        return seat;
    }

    /**
     * 记录用户计时自习室历史记录
     */
    private TimeLearnRoomSeatsBackUpDO recordSeatInfo(Long joinRoomId, TimeLearnRoomSeatsDO seat) {
        TimeLearnRoomSeatsBackUpDO seatBackUp = TimeLearnRoomSeatsBackUpDO.from(seat);
        assert seatBackUp != null;
        seatBackUp.setJoinRoomId(joinRoomId);
        seatBackUp.setSitUpTime(new Date());
        return timeLearnRoomSeatsBackUpRepository.save(seatBackUp);
    }


    @Transactional(rollbackFor = Exception.class)
    public TimeLearnRoomDO lockRoom(Long roomId) {
        return timeLearnRoomRepository.findById(roomId);
    }

    @Override
    public Integer usedSeatsNum(Long roomId) {

        Asserts.notNull(roomId, "roomId不能为空！");
        return timeLearnRoomSeatsRepository.countByRoomIdAndUserIdNotNull(roomId);
    }

    @Override
    public void heart(Long roomId, Long userId) throws UnRoomUserException {
        timeLearnRoomDialogStore.add(roomId, userId);
    }

    @Override
    public TimeLearnRoomVO detail(Long roomId) {

        TimeLearnRoomVO room = TimeLearnRoomVO.from(timeLearnRoomRepository.findOne(roomId));
        Asserts.notNull(room, "没有查询到对应的房间信息 roomId = " + roomId);
        List<TimeLearnRoomSeatsVO> seats = allSeats(roomId)
                .stream()
                .sorted(Comparator.comparing(timeLearnRoomSeatsVO -> timeLearnRoomSeatsVO != null ? timeLearnRoomSeatsVO.getNum() : 0))
                .collect(Collectors.toList());

        room.setSeats(seats);
        //填充座位上用户信息数据
        supplyUserInfo(room);
        int usedSeatNum = (int) seats.stream().filter(s -> Objects.nonNull(s.getUserId())).count();
        room.setUsedSeatsNum(usedSeatNum);
        return room;

    }

    private void supplyUserInfo(TimeLearnRoomVO room) {

        List<TimeLearnRoomSeatsVO> seats = room.getSeats();
        List<Long> userIds = seats
                .stream()
                .map(TimeLearnRoomSeatsVO::getUserId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        if (userIds.isEmpty()) {
            return;
        }
        List<UserVO> users = userClient.list(userIds).getData();
        if (Objects.isNull(users) || users.isEmpty()) {
            return;
        }

        Long hostUserId = BaseController.currentUserId();
        List<UserRelationVO> relations = userFriendClient.relation(hostUserId, userIds).getData();
        seats.forEach(seat -> users
                .stream()
                .filter(u -> Objects.nonNull(seat.getUserId()) && u.getId().equals(seat.getUserId()))
                .forEach(u -> {
                    if (Objects.nonNull(relations)) {
                        UserRelationVO relation = relations
                                .stream()
                                .filter(r -> u.getId().equals(r.getUserId()))
                                .findFirst()
                                .orElse(null);
                        seat.setIsFriend(Objects.nonNull(relation) ? relation.getFriendStatus() : YN.NO.getCode());
                        seat.setIsFans(Objects.nonNull(relation) ? relation.getFollowStatus() : YN.NO.getCode());
                    }
                    seat.setAvatarUrl(u.getAvatarUrl());
                    seat.setSex(u.getSex());
                    seat.setName(u.getName());
                    seat.setSelf(seat.getUserId().equals(hostUserId) ? YN.YES.getCode() : YN.NO.getCode());
                }));
    }


    @Override
    public List<TimeLearnRoomVO> list(Integer type) {
        return timeLearnRoomRepository.findByType(type)
                .stream()
                .map(TimeLearnRoomVO::from)
                .collect(Collectors.toList());
    }

    @Override
    public TimeLearnRoomSeatsDO getTimeLearnRoomSeatsByUserId(Long userId) {
        return timeLearnRoomSeatsRepository.findByUserId(userId);
    }

    @Override
    public Page<TimeLearnRoomUsersDO> findUsers(Long roomId, Pageable page) {
        return timeLearnRoomUsersRepository.findByRoomId(roomId, page);
    }

    @Override
    public List<TimeLearnRoomDO> allRooms() {
        return timeLearnRoomRepository.findAll();
    }

    public TimeLearnRoomUsersDO findByUserId(Long userId) {
        return timeLearnRoomUsersRepository.findByUserId(userId);
    }

    @Override
    public List<TimeLearnRoomSeatsVO> allSeats(Long roomId) {

        return timeLearnRoomSeatsRepository.findByRoomId(roomId)
                .stream()
                .map(TimeLearnRoomSeatsVO::from)
                .collect(Collectors.toList());
    }

    @Override
    public TimeLearnRoomDO getSimpleRoomInfo(Long id) {
        if (timeLearnRooms.size() == 0) {
            synchronized (this) {
                if (timeLearnRooms.size() == 0) {
                    findAll();
                }
            }
        }
        TimeLearnRoomDO room = timeLearnRooms.getIfPresent(id);
        if (Objects.isNull(room)) {
            synchronized (this) {
                findAll();
            }
        }
        return timeLearnRooms.getIfPresent(id);
    }

    @Override
    public List<TimeLearnRoomSeatsVO> findExpireUsers() {
        return timeLearnRoomSeatsRepository.findExpireUsers()
                .stream()
                .map(TimeLearnRoomSeatsVO::from)
                .collect(Collectors.toList());
    }

    @Override
    public Integer sumUsedSeatsNum() {
        return timeLearnRoomSeatsRepository.sumUsedSeatsNum();
    }

    private void findAll() {
        timeLearnRoomRepository.findAll().forEach(r -> timeLearnRooms.put(r.getId(), r));
    }

    @Autowired
    private TimeLearnRoomRepository timeLearnRoomRepository;
    @Autowired
    private TimeLearnRoomSeatsRepository timeLearnRoomSeatsRepository;
    @Autowired
    private TimeLearnRoomUsersRepository timeLearnRoomUsersRepository;
    //业务后置处理器，处理分支业务，如：消息处理、会话管理、学习生活记录等
    @Autowired
    private JoinRoomPostProcessorChain<TimeLearnRoomUsersDO> joinRoomPostProcessorChain;
    @Autowired
    private QuitRoomPostProcessorChain<TimeLearnRoomUsersDO> quitRoomPostProcessorChain;
    @Autowired
    private TakeSeatPostProcessorChain<TimeLearnRoomSeatsDTO> takeSeatPostProcessorChain;
    @Autowired
    private SitUpPostProcessorChain<TimeLearnRoomSeatsDTO> sitUpPostProcessorChain;
    //记录历史数据
    @Autowired
    private TimeLearnRoomUsersBackUpRepository timeLearnRoomUsersBackUpRepository;
    @Autowired
    private TimeLearnRoomSeatsBackUpRepository timeLearnRoomSeatsBackUpRepository;
    //计时自习室-会话仓库
    @Autowired
    private TimeLearnRoomDialogStore timeLearnRoomDialogStore;
    @Autowired
    private UserClient userClient;
    @Autowired
    private UserFriendClient userFriendClient;
    /**
     * 房间基本信息缓存
     */
    private static Cache<Long, TimeLearnRoomDO> timeLearnRooms = CacheBuilder.newBuilder()
            .expireAfterWrite(600, TimeUnit.SECONDS)
            .maximumSize(5000)
            .build();

}
